Skip to content

Conversation

@ChristopherBiscardi
Copy link
Contributor

@ChristopherBiscardi ChristopherBiscardi commented Dec 29, 2025

Objective

People have been asking how to get a compute shader-built mesh into bevy's "stuff".

Some people want to control the lifetime of the mesh via Handle, and others don't don't how to set data in bind groups.

Solution

a new example that shows how to initialize a mesh handle with a render_world usage mesh, and then put the output of the compute shader into the mesh_allocator slab for the mesh.

The demo creates a scene with a camera, light, a circular base mesh, and an empty "cube to be" mesh that is shared by cloning the handle across two entities. The compute shader then fills in the data directly into the mesh_allocator slabs for the vertex/index buffers.

If the compute shader failed, there would be no cube meshes showing as the data would be empty.

Testing

cargo run --example compute_mesh

Showcase

screenshot-2025-12-29-at-16 06 48@2x

@github-actions
Copy link
Contributor

The generated examples/README.md is out of sync with the example metadata in Cargo.toml or the example readme template. Please run cargo run -p build-templated-pages -- update examples to update it, and commit the file change.

@ChristopherBiscardi ChristopherBiscardi marked this pull request as ready for review December 29, 2025 19:01
@ChristopherBiscardi ChristopherBiscardi added A-Rendering Drawing game state to the screen S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Dec 29, 2025
@ChristopherBiscardi ChristopherBiscardi added the C-Examples An addition or correction to our examples label Dec 29, 2025
@alice-i-cecile alice-i-cecile added X-Uncontroversial This work is generally agreed upon D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes labels Dec 29, 2025
// Add the compute node as a top-level node to the render graph. This means it will only execute
// once per frame. Normally, adding a node would use the `RenderGraphApp::add_render_graph_node`
// method, but it does not allow adding as a top-level node.
render_graph.add_node(ComputeNodeLabel, ComputeNode::default());
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't remember if this is right or not. I think you may need to order it vs the camera driver node?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the edge here, but I'm really not sure it has any impact here (but it definitely doesn't hurt). Figuring out what the render graph actually looks like could use some debug visualization. Maybe a function that returns a dotviz 🤔

Copy link
Member

@tychedelia tychedelia left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

thanks!! i've wanted this for a while but only hesitated because i've felt like it should be easier. i have some ideas though and so think this will be a good target to improve things incrementally here

render_app
.world_mut()
.resource_mut::<MeshAllocator>()
.extra_buffer_usages = BufferUsages::STORAGE;
Copy link
Contributor

@IceSentry IceSentry Dec 29, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This feels a bit off. It seems weird that this needs to modify a global resource. But I'm not sure if there's a better alternative

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It feels like something that should be configurable by inserting a Resource or similar rather than modifying this one, yeah. but the MeshAllocator fields are private and initializing it using its FromWorld implementation is probably more awkward (it also has ordering requirements with other resources).

I also wanted to pass the BufferSlice to the shader instead of the full buffer, but I don't think the offsets match the storage buffer offset requirements (it was 256 on my machine and the offsets I saw were numbers like 4, 12, etc)

@IceSentry
Copy link
Contributor

Could you make it so the cubes aren't clipping the circle mesh? I don't generally comment about the look of examples but it looks a bit strange in it's current state.

Also, if I'm reading this correctly this ends up modifying the mesh every frame right? I'd suggest maybe changing the scale every frame to show this is happening.

@ChristopherBiscardi
Copy link
Contributor Author

Could you make it so the cubes aren't clipping the circle mesh?

Updated.

Also, if I'm reading this correctly this ends up modifying the mesh every frame right? I'd suggest maybe changing the scale every frame to show this is happening.

I instead updated this so that it will only generate once per unique GenerateMesh. I envision this particular example to be more of a "generate the N chunks around a player" as they move through a world, so continuing to generate every frame doesn't make sense.

@ChristopherBiscardi
Copy link
Contributor Author

A bug was found where the program was not waiting for the pipeline to be ready, and on windows/linux vulkan the pipeline takes a few frames to be ready (on macos it is ready immediately). The program now waits before attempting to send applicable meshes and considering them "processed".


mesh.asset_usage = RenderAssetUsages::RENDER_WORLD;
mesh
};
Copy link
Member

@Aceeri Aceeri Jan 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Kinda sucks that you still have to transmit this once, would be nice if there was a way to just tell it to zero out a region for you. Not really related to this PR though.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You do in this example which is intended to be the "simplest" approach to getting a compute-shader powered mesh into the "regular" bevy handling just like every other mesh, not the most performant. This example only requires setting up: the mesh data you expect to write and the compute shader to write it, and in return you get compatibility with all the things that use Mesh3d in their queries (lights, etc).

Obviously if you skip bevy's handling, opt-out of standardmaterial, and go compute-shader -> custom vertex/fragment pipeline, or use mesh shaders in the future, then everything can stay on the gpu and passing meshes around becomes significantly simpler (custom phase item example is a good showcase of that). Maybe there's another compute shader example to make there.

The there's a bunch of examples in the shader_advanced section that show off how to do various things, but bevy's built-in draw commands for specializedmeshpipeline, etc assume meshallocator participation and extraction assumes mesh3d.

name = "Compute Shader Mesh"
description = "A compute shader that generates a mesh that is controlled by a Handle"
category = "Shaders"
wasm = false
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

would this one work in WebGPU but not WebGL2?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yes, it can't work in webgl2 because it uses a compute shader which isn't available in webgl2.

@Aceeri
Copy link
Member

Aceeri commented Jan 12, 2026

Very nice addition, I was trying to figure this out earlier myself but this lays it out pretty clearly :)

@ChristopherBiscardi ChristopherBiscardi added S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it and removed S-Needs-Review Needs reviewer attention (from anyone!) to move forward labels Jan 15, 2026
@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jan 15, 2026
github-merge-queue bot pushed a commit that referenced this pull request Jan 15, 2026
# Objective

People have been asking how to get a compute shader-built mesh into
bevy's "stuff".

Some people want to control the lifetime of the mesh via Handle, and
others don't don't how to set data in bind groups.

## Solution

a new example that shows how to initialize a mesh handle with a
render_world usage mesh, and then put the output of the compute shader
into the mesh_allocator slab for the mesh.

The demo creates a scene with a camera, light, a circular base mesh, and
an empty "cube to be" mesh that is shared by cloning the handle across
two entities. The compute shader then fills in the data directly into
the mesh_allocator slabs for the vertex/index buffers.

If the compute shader failed, there would be no cube meshes showing as
the data would be empty.

## Testing

```
cargo run --example compute_mesh
```

---

## Showcase


<img width="3392" height="2106" alt="screenshot-2025-12-29-at-16 06
48@2x"
src="https://github.com/user-attachments/assets/88d8fed4-e3c1-418e-bb04-6f08d673403a"
/>
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jan 15, 2026
@mockersf mockersf enabled auto-merge January 15, 2026 21:26
@mockersf mockersf added this pull request to the merge queue Jan 15, 2026
@github-merge-queue github-merge-queue bot removed this pull request from the merge queue due to failed status checks Jan 15, 2026
@ChristopherBiscardi
Copy link
Contributor Author

linux failure seems unrelated to the PR

@alice-i-cecile alice-i-cecile added this pull request to the merge queue Jan 16, 2026
Merged via the queue into bevyengine:main with commit 71ce303 Jan 16, 2026
42 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

A-Rendering Drawing game state to the screen C-Examples An addition or correction to our examples D-Modest A "normal" level of difficulty; suitable for simple features or challenging fixes S-Ready-For-Final-Review This PR has been approved by the community. It's ready for a maintainer to consider merging it X-Uncontroversial This work is generally agreed upon

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

7 participants